package Clojure

import (
	"fmt"
	. "misc/Clojure/parse"
)

var MainContext *Context

type Context struct {
	symbols map[string]Node
	parent  *Context
}

var astTrees map[string]*Tree

func LoadAstTree(fileName, strKey string) {
	if astTrees == nil {
		astTrees = make(map[string]*Tree)
	}
	t, err := File(fileName, false)
	if err != nil {
		fmt.Println(err)
	}
	astTrees[strKey] = t
}
func GetAstTree(fileName string) *Tree {
	return astTrees[fileName]
}

func NewContext(parent *Context) *Context {
	return &Context{
		make(map[string]Node),
		parent,
	}
}
func (c *Context) Import(other *Context, prefix string) {
	for key, value := range other.symbols {
		c.symbols[prefix+key] = value
	}

}

func (c *Context) Define(symbolName string, node Node) {
	c.symbols[symbolName] = node
}
func (c *Context) LookUp(symbolName string) Node {
	val, defined := c.symbols[symbolName]
	if defined {
		return val
	} else if c.parent != nil {
		return c.parent.LookUp(symbolName)
	} else {
		return nil
	}
}

func InitRuntime() {
	MainContext = CreateMainContext()
	defineCoreFunctions()
}

func CreateMainContext() *Context {
	context := NewContext(nil)
	context.Define("log", NativeFunction{_log})
	return context
}

func EvaluateTree(tree *Tree, context *Context) {
	for i := 0; i < len(tree.Roots); i++ {
		Evaluate(tree.Roots[i], context)
	}
}

/*         list
symbol(log)         list
              symbol(+) number(1) number(1)
(log (+ 1 1))
*/
func Evaluate(node Node, context *Context) Node {
	switch node.(type) {
	case *ListNode:
		if symNode, ok := node.Children()[0].(*SymbolNode); ok {
			fnNode := context.LookUp(symNode.Val)
			if fnNode != nil {
				if fn, ok := fnNode.(NativeFunction); ok {
					return fn.Call(node.Children()[1:], context)
				}
			} else {
				fmt.Println("Use Not Define SymbolNode " + node.String() + " :" + symNode.Val + ":")
			}
		} else {
			fmt.Println("ListNode first Node not SymbolNode")
		}
	case *SymbolNode:
		symNode := node.(*SymbolNode)
		return context.LookUp(symNode.Val)
	default:
		return node
	}
	return nil
}
